package com.easygoods.service;

import com.bestvike.linq.Linq;
import com.bestvike.stone.core.lang.RandomUtils;
import com.bestvike.stone.core.lang.StringUtils;
import com.bestvike.stone.core.time.DateUtils;
import com.bestvike.stone.spring.exception.BusinessException;
import com.bestvike.stone.spring.exception.ErrorCode;
import com.bestvike.stone.spring.service.BaseService;
import com.easygoods.bean.MemberGradeInfo;
import com.easygoods.dao.MemberInfoDao;
import com.easygoods.dao.MemberPointDao;
import com.easygoods.dao.MemberPointRecordDao;
import com.easygoods.entity.MemberInfo;
import com.easygoods.entity.MemberPoint;
import com.easygoods.entity.MemberPointRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import tk.mybatis.mapper.entity.Example;

import java.math.BigDecimal;
import java.util.Calendar;
import java.util.List;

//积分服务
@Service
public class PointService extends BaseService {
    @Autowired
    private MemberInfoDao memberInfoDao;
    @Autowired
    private ConfigService configService;
    @Autowired
    private MemberPointRecordDao memberPointRecordDao;
    @Autowired
    private MemberService memberService;
    @Autowired
    private MemberPointDao memberPointDao;

    //修改用户积分和用户等级
    private MemberGradeInfo updateMemberPointAndGrade(String memberId, int leftPointValue) {
        MemberGradeInfo memberGradeInfo = this.computeGrade(leftPointValue);//重新计算用户等级
        MemberInfo memberInfoUpdate = new MemberInfo();
        memberInfoUpdate.setId(memberId);
        memberInfoUpdate.setGrade(memberGradeInfo.getGrade());
        memberInfoUpdate.setTotalPoint(leftPointValue);
        memberInfoUpdate.beforeUpdate();
        final int count = this.memberInfoDao.updateByPrimaryKeySelective(memberInfoUpdate);
        if (count != 1) {
            this.log.error("更新积分失败 memberId:" + memberId);
            throw new BusinessException(ErrorCode.ERROR_SERVER, "更新积分失败");
        }
        return memberGradeInfo;
    }

    //对指定积分记录进行扣减,返回还要继续扣减的积分,等级需要在调用的方法里自行处理
    private int decreasePointWithoutGrade(MemberPoint memberPoint, int pointValue) {
        Assert.notNull(memberPoint, "memberPoint can not be null");
        Assert.isTrue(pointValue >= 0, "pointValue must greater than 0");
        if (pointValue == 0)
            return 0;
        //该年份积分已经 <=0
        final Integer totalPoint = memberPoint.getTotalPoint();
        if (totalPoint <= 0)
            return pointValue;

        //足够扣减
        if (totalPoint >= pointValue) {
            int leftPoint = totalPoint - pointValue;
            MemberPoint memberPointUpdate = new MemberPoint();
            memberPointUpdate.setId(memberPoint.getId());
            memberPointUpdate.setTotalPoint(leftPoint);
            memberPointUpdate.beforeUpdate();
            final int updateCount = this.memberPointDao.updateByPrimaryKeySelective(memberPointUpdate);
            if (updateCount != 1) {
                this.log.error("修改积分记录失败");
                throw new BusinessException(ErrorCode.ERROR_SERVER, "操作失败");
            }
            return 0;
        }

        //不够扣减
        MemberPoint memberPointUpdate = new MemberPoint();
        memberPointUpdate.setId(memberPoint.getId());
        memberPointUpdate.setTotalPoint(0);
        memberPointUpdate.beforeUpdate();
        final int updateCount = this.memberPointDao.updateByPrimaryKeySelective(memberPointUpdate);
        if (updateCount != 1) {
            this.log.error("修改积分记录失败");
            throw new BusinessException(ErrorCode.ERROR_SERVER, "操作失败");
        }
        return pointValue - totalPoint;
    }

    //计算等级信息
    public MemberGradeInfo computeGrade(int point) {
        //当前等级计算
        Integer[] gradeLowerArray = this.configService.getGradeLowerArray();
        String[] gradeNameArray = this.configService.getGradeNameArray();
        int grade;
        final int maxGrade = gradeLowerArray.length - 1;
        for (grade = maxGrade; grade >= 0; grade--) {
            if (point >= gradeLowerArray[grade])
                break;
        }
        //
        MemberGradeInfo memberGradeInfo = new MemberGradeInfo();
        memberGradeInfo.setGrade(grade);
        memberGradeInfo.setGradeName(gradeNameArray[grade]);
        memberGradeInfo.setNextGrade(grade + 1);
        memberGradeInfo.setNextGradeName(grade == maxGrade ? StringUtils.EMPTY : gradeNameArray[grade + 1]);
        memberGradeInfo.setPoint(point);
        memberGradeInfo.setPointUpper(grade == maxGrade ? 999999999 : gradeLowerArray[grade + 1]);
        memberGradeInfo.setPointLower(gradeLowerArray[grade]);
        return memberGradeInfo;
    }

    //根据商品会员价格计算商品积分
    public int computeGoodsPointValue(BigDecimal memberPrice) {
        Assert.notNull(memberPrice, "memberPrice can not be null");
        Assert.isTrue(memberPrice.compareTo(BigDecimal.ZERO) > 0, "memberPrice can not be smaller than zero");

        int memberPriceInt = memberPrice.intValue();
        int pointX = this.configService.getPointX();
        int pointPerHundred = this.configService.getPointPerHundred();
        int pointMax = this.configService.getPointMax();
        int point = memberPriceInt <= pointX
                ? memberPriceInt//未到分界点
                : memberPriceInt + (memberPriceInt - pointX) / 100 * pointPerHundred;//

        //不能超过最大值
        return point > pointMax ? pointMax : point;
    }

    //用户增加积分
    @Transactional
    public String increasePoint(String memberId, int pointValue, String reason, String gainOrderId) {
        Assert.hasText(memberId, "memberId can not be empty");
        Assert.isTrue(pointValue > 0, "pointValue must greater than zero");

        //更新用户积分
        MemberInfo memberInfo = this.memberService.getMemberInfoForUpdate(memberId);
        int totalPoint = memberInfo.getTotalPoint() + pointValue;

        //更新用户积分和等级
        this.updateMemberPointAndGrade(memberId, totalPoint);

        //更新用户年份积分
        int year = DateUtils.nowCalendar().get(Calendar.YEAR);
        Example example = new Example(MemberPoint.class);
        example.createCriteria()
                .andEqualTo(MemberPoint.MEMBER_ID, memberId)
                .andEqualTo(MemberPoint.GAIN_YEAR, year);
        MemberPoint memberPoint = this.memberPointDao.selectOneByExample(example);
        if (memberPoint == null) {//没有执行 insert
            MemberPoint memberPointNew = new MemberPoint();
            memberPointNew.setId(RandomUtils.randomUUID());
            memberPointNew.setMemberId(memberId);
            memberPointNew.setGainYear(year);
            memberPointNew.setTotalPoint(pointValue);
            memberPointNew.beforeInsert();
            this.memberPointDao.insert(memberPointNew);
        } else {//有的话 update
            MemberPoint memberPointUpdate = new MemberPoint();
            memberPointUpdate.setId(memberPoint.getId());
            memberPointUpdate.setTotalPoint(memberPoint.getTotalPoint() + pointValue);
            memberPointUpdate.beforeUpdate();
            this.memberPointDao.updateByPrimaryKeySelective(memberPointUpdate);
        }

        //新增积分流水
        MemberPointRecord memberPointRecord = new MemberPointRecord();
        memberPointRecord.setId(RandomUtils.randomUUID());
        memberPointRecord.setMemberId(memberId);
        memberPointRecord.setPointValue(pointValue);
        memberPointRecord.setReason(reason);
        memberPointRecord.setRefGainOrderId(gainOrderId);
        memberPointRecord.setRefPayOrderId(null);
        memberPointRecord.beforeInsert();
        this.memberPointRecordDao.insert(memberPointRecord);
        return memberPointRecord.getId();
    }

    //消费积分
    @Transactional
    public void consumePoint(String memberId, int pointValue, String reason, String payOrderId) {
        Assert.hasText(memberId, "memberId can not be empty");
        Assert.isTrue(pointValue > 0, "pointValue must greater than zero");

        //查询锁定用户信息
        MemberInfo memberInfo = this.memberService.getMemberInfoForUpdate(memberId);
        int leftPointValue = memberInfo.getTotalPoint() - pointValue;//消费后剩余积分
        if (leftPointValue < 0)
            throw new BusinessException(ErrorCode.ERROR_SERVER, "积分不足");

        //更新用户剩余积分,等级
        this.updateMemberPointAndGrade(memberId, leftPointValue);

        //查询要删除的
        Example example = new Example(MemberPoint.class);
        example.createCriteria().andEqualTo(MemberPoint.MEMBER_ID, memberId);
        example.orderBy(MemberPoint.GAIN_YEAR).asc();
        List<MemberPoint> memberPoints = this.memberPointDao.selectByExample(example);
        int toDecreasePoint = pointValue;
        for (MemberPoint item : memberPoints) {
            if (toDecreasePoint <= 0)
                break;
            toDecreasePoint = this.decreasePointWithoutGrade(item, toDecreasePoint);
        }
        if (toDecreasePoint > 0) {
            this.log.error(String.format("用户 %s 的总积分 %d 够消费 %d ,但年份积分不够消费", memberId, memberInfo.getTotalPoint(), pointValue));
            throw new BusinessException(ErrorCode.ERROR_SERVER, "消费积分失败");
        }
        //记录日志
        MemberPointRecord memberPointRecord = new MemberPointRecord();
        memberPointRecord.setId(RandomUtils.randomUUID());
        memberPointRecord.setMemberId(memberId);
        memberPointRecord.setPointValue(-pointValue);
        memberPointRecord.setReason(reason);
        memberPointRecord.setRefGainOrderId(null);
        memberPointRecord.setRefPayOrderId(payOrderId);
        memberPointRecord.beforeInsert();
        final int insert = this.memberPointRecordDao.insert(memberPointRecord);
        if (insert != 1) {
            this.log.error("记录积分日志失败 memberId:" + memberId);
            throw new BusinessException(ErrorCode.ERROR_SERVER, "消费积分失败");
        }
    }

    //扣减积分,退货
    @Transactional
    public void deductPoint(String pointRecordId, String reason) {
        Assert.hasText(pointRecordId, "pointRecordId can not be empty");

        MemberPointRecord memberPointRecord = this.memberPointRecordDao.selectByPrimaryKey(pointRecordId);
        if (memberPointRecord == null) {
            this.log.error("扣减积分失败 pointRecordId:" + pointRecordId);
            throw new BusinessException(ErrorCode.ERROR_SERVER, "操作失败");
        }
        final String memberId = memberPointRecord.getMemberId();
        final Integer pointValue = memberPointRecord.getPointValue();

        final int yearOfGainPoint = DateUtils.toCalendar(memberPointRecord.getCreateTime()).get(Calendar.YEAR);
        Assert.notNull(memberId, "memberId 不能为 null");
        Assert.notNull(pointValue, "原始积分为 null,无法扣减");
        Assert.isTrue(pointValue > 0, "原始积分必须大于 0");

        //更新用户积分
        MemberInfo memberInfo = this.memberService.getMemberInfoForUpdate(memberId);
        int totalPoint = memberInfo.getTotalPoint() - pointValue;

        //更新用户总积分,等级
        this.updateMemberPointAndGrade(memberId, totalPoint);

        //查询要删除的
        Example example = new Example(MemberPoint.class);
        example.createCriteria().andEqualTo(MemberPoint.MEMBER_ID, memberId);
        example.orderBy(MemberPoint.GAIN_YEAR).desc();
        List<MemberPoint> memberPoints = this.memberPointDao.selectByExample(example);
        int toDeductPoint = pointValue;//待扣减的积分
        //获得积分的那年的积分汇总
        final MemberPoint memberPointOfGainPoint = Linq.asEnumerable(memberPoints).firstOrDefault(a -> a.getGainYear() == yearOfGainPoint);
        if (memberPointOfGainPoint != null)
            toDeductPoint = this.decreasePointWithoutGrade(memberPointOfGainPoint, toDeductPoint);
        //如果不够扣减,按时间最近的到远的扣减
        if (toDeductPoint > 0) {
            for (MemberPoint item : memberPoints) {
                if (toDeductPoint <= 0)
                    break;
                toDeductPoint = this.decreasePointWithoutGrade(item, toDeductPoint);
            }
        }
        //依然不够扣的,当年积分上负数
        if (toDeductPoint > 0) {
            //更新用户年份积分
            int year = DateUtils.nowCalendar().get(Calendar.YEAR);
            final Example exampleCurrentYear = new Example(MemberPoint.class);
            example.createCriteria()
                    .andEqualTo(MemberPoint.MEMBER_ID, memberId)
                    .andEqualTo(MemberPoint.GAIN_YEAR, year);
            MemberPoint memberPointCurrentYear = this.memberPointDao.selectOneByExample(exampleCurrentYear);
            if (memberPointCurrentYear == null) {//没有执行 insert
                MemberPoint memberPointNew = new MemberPoint();
                memberPointNew.setId(RandomUtils.randomUUID());
                memberPointNew.setMemberId(memberId);
                memberPointNew.setGainYear(year);
                memberPointNew.setTotalPoint(-toDeductPoint);
                memberPointNew.beforeInsert();
                this.memberPointDao.insert(memberPointNew);
            } else {//有的话 update
                MemberPoint memberPointUpdate = new MemberPoint();
                memberPointUpdate.setId(memberPointCurrentYear.getId());
                memberPointUpdate.setTotalPoint(memberPointCurrentYear.getTotalPoint() - toDeductPoint);
                memberPointUpdate.beforeUpdate();
                this.memberPointDao.updateByPrimaryKeySelective(memberPointUpdate);
            }
        }

        //记录日志
        MemberPointRecord memberPointRecordInsert = new MemberPointRecord();
        memberPointRecordInsert.setId(RandomUtils.randomUUID());
        memberPointRecordInsert.setMemberId(memberId);
        memberPointRecordInsert.setPointValue(-pointValue);
        memberPointRecordInsert.setReason(reason);
        memberPointRecordInsert.setRefGainOrderId(memberPointRecord.getRefGainOrderId());
        memberPointRecordInsert.setRefPayOrderId(memberPointRecord.getRefPayOrderId());
        memberPointRecordInsert.beforeInsert();
        final int insert = this.memberPointRecordDao.insert(memberPointRecordInsert);
        if (insert != 1) {
            this.log.error("新增用户积分记录失败");
            throw new BusinessException(ErrorCode.ERROR_SERVER, "操作失败");
        }
    }

    //积分过期
    @Transactional
    public void expirePoint(String memberId, int keepYears) {
        Assert.hasText(memberId, "memberId can not be empty");
        Assert.isTrue(keepYears >= 2, "keepYears must greater than or equals to 2");

        int currentYear = DateUtils.nowCalendar().get(Calendar.YEAR);
        int expireYear = currentYear - keepYears;

        //查询锁定用户信息
        final MemberInfo memberInfo = this.memberService.getMemberInfoForUpdate(memberId);
        //查询要删除的
        int delPointValue = this.memberPointDao.sumExpirePoint(memberId, expireYear);//要删除的积分
        if (delPointValue <= 0)
            return;
        int leftPointValue = this.memberPointDao.reSumPoint(memberId);//清除后剩余积分

        //更新用户等级
        final MemberGradeInfo gradeInfo = this.updateMemberPointAndGrade(memberId, leftPointValue);

        //删除积分年份表
        Example example = new Example(MemberPoint.class);
        example.createCriteria().andEqualTo(MemberPoint.MEMBER_ID, memberId)
                .andLessThanOrEqualTo(MemberPoint.GAIN_YEAR, expireYear);
        this.memberPointDao.deleteByExample(example);
        //记录日志
        MemberPointRecord memberPointRecord = new MemberPointRecord();
        memberPointRecord.setId(RandomUtils.randomUUID());
        memberPointRecord.setMemberId(memberId);
        memberPointRecord.setPointValue(-delPointValue);
        memberPointRecord.setReason("积分过期");
        memberPointRecord.setRefGainOrderId(null);
        memberPointRecord.setRefPayOrderId(null);
        memberPointRecord.beforeInsert();
        final int insert = this.memberPointRecordDao.insert(memberPointRecord);
        if (insert != 1) {
            this.log.error("记录积分日志失败 memberId:" + memberId);
            throw new BusinessException(ErrorCode.ERROR_SERVER, "积分过期失败");
        }
        this.log.info(String.format("用户 %s(%s), 过期 %d 积分, 剩余 %d 积分, 新等级 %d(%s)", memberId, memberInfo.getNickName(), delPointValue, leftPointValue, gradeInfo.getGrade(), gradeInfo.getGradeName()));
    }
}
